mod config;

pub use config::COLOR_KEY_WORDS;
use regex::Regex;

pub type RbgValue = [u8; 3];
pub type RbgaValue = [u8; 4];

// t: 0-1之间的浮点数
pub fn darken(c: &str, t: f64) -> String {
    let (value, op) = get_rgba_value(c);
    let darken_value = value
        .iter()
        .map(|v| {
            let v = *v as i32;
            let ret = v - f64::ceil(2.55 * t) as i32;
            if ret < 0 {
                0 as u8
            } else {
                ret as u8
            }
        })
        .collect::<Vec<_>>();
    get_color_from_rgb_value(&darken_value, op)
}

pub fn fade(c: &str, t: f64) -> String {
    assert!(t <= 1. && t >= 0.);
    let color = get_rgb_value(c);
    get_color_from_rgb_value(&color, Some(t))
}

pub fn get_color_from_rgb_value(rgb: &[u8], opacity: Option<f64>) -> String {
    if let Some(o) = opacity {
        format!("rgba({},{},{},{})", rgb[0], rgb[1], rgb[2], o)
    } else {
        format!("rgb({},{},{})", rgb[0], rgb[1], rgb[2])
    }
}

pub fn get_opacity(c: &str) -> Option<f64> {
    let (_, o) = get_rgba_value(c);
    o
}

pub fn get_rgb_value(c: &str) -> [u8; 3] {
    get_rgba_value(c).0
}

pub fn get_rgba_value(c: &str) -> ([u8; 3], Option<f64>) {
    let color = is_color(c).to_lowercase();
    if is_hex(&color) {
        (hex_to_int(&color), None)
    } else {
        rgba_to_int(&color)
    }
}

pub fn is_hex(c: &str) -> bool {
    let c = c.to_lowercase();
    let re = Regex::new(r#"^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$"#).unwrap();
    re.is_match(&c)
}

pub fn is_rgb(c: &str) -> bool {
    let c = c.to_lowercase();
    let re = Regex::new(r#"^(rgb\(|RGB\()"#).unwrap();
    re.is_match(&c)
}

pub fn is_rgba(c: &str) -> bool {
    let c = c.to_lowercase();
    let re = Regex::new(r#"^(rgba|RGBA)"#).unwrap();
    re.is_match(&c)
}

pub fn is_rgb_or_rgba(c: &str) -> bool {
    let c = c.to_lowercase();
    let re = Regex::new(r#"^(rgb|rgba|RGB|RGBA)"#).unwrap();
    re.is_match(&c)
}

pub fn lighten(c: &str, t: f64) -> String {
    let (color, o) = get_rgba_value(c);
    let value = color
        .iter()
        .map(|v| {
            let v = *v as u32;

            let ret = v + f64::ceil(2.55 * t) as u32;
            if ret > 255 {
                255 as u8
            } else {
                ret as u8
            }
        })
        .collect::<Vec<_>>();
    get_color_from_rgb_value(&value, o)
}

pub fn to_hex(c: &str) -> String {
    if is_hex(c) {
        return c.to_owned();
    }
    let rgb = get_rgb_value(c);
    format!("#{:02x}{:02x}{:02x}", rgb[0], rgb[1], rgb[2])
}

pub fn to_rgb(c: &str) -> String {
    let (value, op) = get_rgba_value(c);
    if let Some(o) = op {
        format!("rgba({},{},{},{})", value[0], value[1], value[2], o)
    } else {
        format!("rgb({},{},{})", value[0], value[1], value[2])
    }
}

fn hex_to_int(c: &str) -> [u8; 3] {
    let c = c.replace("#", "");
    let bytes = c.as_bytes();
    let mut ret = [0u8; 3];
    if bytes.len() == 3 {
        for (i, v) in bytes.iter().enumerate() {
            let value = get_value(*v) & 0x0f;
            let value = value << 4 | value;
            ret[i] = value;
        }
        return ret;
    }
    if c.len() == 6 {
        for (i, chunk) in bytes.chunks(2).into_iter().enumerate() {
            let value_hight = get_value(chunk[0]) & 0xf;
            let value_low = get_value(chunk[1]) & 0x0f;
            let value = value_hight << 4 | value_low;
            ret[i] = value;
        }
        return ret;
    }

    panic!("the length of value is wrong");
}

fn is_color<'a>(c: &'a str) -> &'a str {
    if is_hex(c) || is_rgb_or_rgba(c) {
        return c;
    }
    let v = config::COLOR_KEY_WORDS.get(c);
    *v.unwrap()
}

fn rgba_to_int(c: &str) -> ([u8; 3], Option<f64>) {
    let re = regex::Regex::new(r#"rgb\(|rgba\(|\)"#).unwrap();
    let value = re.replace_all(c, "");
    let iter = value.split(",");

    let mut ret = [0u8; 3];
    let mut o = None;
    for (i, v) in iter.enumerate() {
        if i == 3 {
            o = Some(v.trim().parse::<f64>().unwrap());
        } else {
            ret[i] = v.trim().parse::<u8>().unwrap();
        }
    }
    (ret, o)
}

fn get_value(c: u8) -> u8 {
    if c >= b'a' && c <= b'f' {
        c - b'a' + 10u8
    } else if c >= b'0' && c <= b'9' {
        c - b'0'
    } else {
        panic!("char wrong");
    }
}

#[cfg(test)]
mod tests {
    use crate::{
        get_color_from_rgb_value, get_opacity, get_rgb_value, get_rgba_value, is_hex, is_rgb,
        is_rgb_or_rgba, is_rgba, to_hex, to_rgb,
    };

    #[test]
    fn test_color() {
        let c1 = "#fac";
        let c2 = "#00fcad";
        let c3 = "rgb(123,  43,  34)";
        let c4 = "rgba(34, 56, 255, 0.5)";
        println!("{}", is_hex(c1));
        println!("{}", is_hex(c2));
        println!("{}", is_hex(c3));
        println!("{}", is_hex(c4));

        println!("{}", is_rgb(c1));
        println!("{}", is_rgb(c2));
        println!("{}", is_rgb(c3));
        println!("{}", is_rgb(c4));
        println!("-----------------");
        println!("{}", is_rgba(c1));
        println!("{}", is_rgba(c2));
        println!("{}", is_rgba(c3));
        println!("{}", is_rgba(c4));
        println!("-----------------");
        println!("{}", is_rgb_or_rgba(c1));
        println!("{}", is_rgb_or_rgba(c2));
        println!("{}", is_rgb_or_rgba(c3));
        println!("{}", is_rgb_or_rgba(c4));
        println!("-----------------");
        println!("{:?}", get_rgb_value(c1));
        println!("{:?}", get_rgb_value(c2));
        println!("{:?}", get_rgb_value(c3));
        println!("{:?}", get_rgb_value(c4));
        println!("-----------------");
        println!("{:?}", get_rgba_value(c1));
        println!("{:?}", get_rgba_value(c2));
        println!("{:?}", get_rgba_value(c3));
        println!("{:?}", get_rgba_value(c4));
        println!("-----------------");
        println!("{:?}", get_opacity(c1));
        println!("{:?}", get_opacity(c2));
        println!("{:?}", get_opacity(c3));
        println!("{:?}", get_opacity(c4));
        println!("-----------------");
        let value = get_rgb_value(c1);
        let op = get_opacity(c1);
        println!("{}", get_color_from_rgb_value(&value, op));
        println!("-----------------");
        let value = get_rgb_value(c2);
        let op = get_opacity(c2);
        println!("{}", get_color_from_rgb_value(&value, op));
        println!("-----------------");
        let value = get_rgb_value(c3);
        let op = get_opacity(c3);
        println!("{}", get_color_from_rgb_value(&value, op));
        println!("-----------------");
        let value = get_rgb_value(c4);
        let op = get_opacity(c4);
        println!("{}", get_color_from_rgb_value(&value, op));
        println!("-----------------");
        println!("{}", to_hex(c1));
        println!("{}", to_hex(c2));
        println!("{}", to_hex(c3));
        println!("{}", to_hex(c4));
        println!("-----------------");
        println!("{}", to_rgb(c1));
        println!("{}", to_rgb(c2));
        println!("{}", to_rgb(c3));
        println!("{}", to_rgb(c4));
    }
}
